package cn.edu.cug.cs.gtl.gsm;

import cn.edu.cug.cs.gtl.common.Identifier;
import cn.edu.cug.cs.gtl.gsm.item.IndexItem;
import cn.edu.cug.cs.gtl.gsm.item.IndexMap;
import cn.edu.cug.cs.gtl.gsm.item.MetaItem;
import cn.edu.cug.cs.gtl.io.storage.StorageManager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class PaginationStorageManager implements StorageManager {
    protected MetaItem metaItem;
    protected IndexMap pageMap;
    protected byte [] pageBuffer;
    protected int currentCollectionOrder;//当前集合序号
    /**
     *
     * @param collectionSize
     * @param pageSize
     */
    public PaginationStorageManager(long collectionSize , int pageSize) {
        this.metaItem = new MetaItem(collectionSize,pageSize);
        this.pageMap = new IndexMap();
        this.pageBuffer = new byte[pageSize];
        currentCollectionOrder=0;
    }

    /**
     * @param baseName
     * @param collectionSize
     * @param pageSize
     */
    public PaginationStorageManager(String baseName, long collectionSize , int pageSize) {
        this.metaItem = new MetaItem(collectionSize,pageSize);
        this.pageMap = new IndexMap();
        this.pageBuffer = new byte[pageSize];
        currentCollectionOrder=0;
        this.metaItem.setBaseName(baseName);
    }

    public int getCurrentCollectionOrder() {
        return currentCollectionOrder;
    }

    public void setCurrentCollectionOrder(int currentCollectionOrder) {
        this.currentCollectionOrder = currentCollectionOrder;
    }

    public int getPageSize(){
        return this.metaItem.getPageSize();
    }

    public long getCollectionSize(){
        return this.metaItem.getCollectionSize();
    }

    public String getBaseName(){return this.metaItem.getBaseName();}

    public List<Identifier> getEmptyPageIdentifiers(){
        return this.metaItem.getEmptyPageIdentifiers();
    }

    public Identifier getNextPageIdentifier(){
        return this.metaItem.getNextPageIdentifier();
    }

    /***
     *
     * @param pageIdentifier
     * @return
     * @throws IOException
     */
    @Override
    public byte[] loadByteArray(Identifier pageIdentifier) throws IOException {
        //查找页面ID是否存在，如果不存在则抛出异常
        IndexItem e = this.pageMap.get(pageIdentifier);
        if (e == null)
            throw new IOException("PaginationStorageManager.loadByteArray" + pageIdentifier.toString());
        //IndexItem中的页面总数
        int cTotal = e.getPageIdentifiers().size();
        //用于存放Entry中记录页面中记录的内容
        byte[] data = new byte[e.getDataLength()];
        //Entry.pages中的下一个页面的下标
        int cNext = 0;
        int ptr = 0;
        int cLen = 0;
        //剩下的需要读取的内存长度
        int cRem = e.getDataLength();

        do {
            readBufferFromPage(e.getPageIdentifiers().get(cNext));
            //计算实际有效内容长度
            cLen = (cRem > this.getPageSize()) ? this.getPageSize() : cRem;
            //将this.buffer中的内容拷贝到data中，长度为cLen
            System.arraycopy(this.pageBuffer, 0, data, ptr, cLen);
            ptr += cLen;
            cRem -= cLen;
            ++cNext;
        } while (cNext < cTotal);

        return data;
    }

    @Override
    public void storeByteArray(Identifier pageIdentifier, byte[] data) throws IOException {
        if (pageIdentifier.longValue() == StorageManager.NEW_PAGE) {
            IndexItem e = new IndexItem(data.length,new ArrayList<>());

            int ptr = 0;//从data数组的第0个字节开始
            Identifier cPage = Identifier.create(StorageManager.NEW_PAGE);
            int cRem = data.length;
            int cLen = 0;

            while (cRem > 0) {
                if (!this.getEmptyPageIdentifiers().isEmpty()) {
                    cPage.reset(this.getEmptyPageIdentifiers().remove(0).longValue());
                } else {
                    cPage.reset(this.getNextPageIdentifier().longValue());
                    this.getNextPageIdentifier().increase();
                }

                cLen = (cRem > this.getPageSize()) ? this.getPageSize() : cRem;
                System.arraycopy(data, ptr, this.pageBuffer, 0, cLen);

                writeBufferToPage(cPage);

                ptr += cLen;
                cRem -= cLen;
                e.getPageIdentifiers().add((Identifier) cPage.clone());
            }

            pageIdentifier.copyFrom(e.getPageIdentifiers().get(0));
            this.pageMap.put((Identifier) pageIdentifier.clone(), e);
        }
        else {
            // find the entry.
            IndexItem oldEntry = this.pageMap.remove(pageIdentifier);
            if (oldEntry == null)
                throw new IOException("PaginationStorageManager.storeByteArray: Invalid Page Exception");

            IndexItem e = new IndexItem(data.length,new ArrayList<>());

            int ptr = 0;
            Identifier cPage = Identifier.create(StorageManager.NEW_PAGE);
            int cRem = e.getDataLength();
            int cLen, cNext = 0;

            while (cRem > 0) {
                if (cNext < oldEntry.getPageIdentifiers().size()) {
                    cPage.copyFrom(oldEntry.getPageIdentifiers().get(cNext));
                    ++cNext;
                } else if (!this.getEmptyPageIdentifiers().isEmpty()) {
                    cPage.copyFrom(this.getEmptyPageIdentifiers().remove(0));
                } else {
                    cPage.copyFrom(this.getNextPageIdentifier());
                    this.getNextPageIdentifier().increase();
                }

                cLen = (cRem > this.getPageSize()) ? this.getPageSize() : cRem;
                System.arraycopy(data, ptr, this.pageBuffer, 0, cLen);

                writeBufferToPage(cPage);

                ptr += cLen;
                cRem -= cLen;
                e.getPageIdentifiers().add((Identifier) cPage.clone());
            }

            while (cNext < oldEntry.getPageIdentifiers().size()) {
                this.getEmptyPageIdentifiers().add((Identifier) oldEntry.getPageIdentifiers().get(cNext).clone());
                ++cNext;
            }
            this.pageMap.put((Identifier) pageIdentifier.clone(), e);
        }
    }

    /***
     *
     * @param pageIdentifier
     * @throws IOException
     */
    @Override
    public void deleteByteArray(Identifier pageIdentifier) throws IOException {
        IndexItem e = this.pageMap.remove(pageIdentifier);
        if (e == null)
            throw new IOException("PaginationStorageManager.deleteByteArray: " + pageIdentifier.toString());

        for (int cIndex = 0; cIndex < e.getPageIdentifiers().size(); ++cIndex) {
            this.getEmptyPageIdentifiers().add((Identifier) e.getPageIdentifiers().get(cIndex).clone());
        }
    }

    @Override
    public void flush() throws IOException {
        writeMetaToStorage();
        writeIndicesToStorage();
    }

    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * <p> As noted in {@link AutoCloseable#close()}, cases where the
     * close may fail require careful attention. It is strongly advised
     * to relinquish the underlying resources and to internally
     * <em>mark</em> the {@code Closeable} as closed, prior to throwing
     * the {@code IOException}.
     *
     * @throws IOException if an I/O error occurs
     */
    @Override
    public void close() throws IOException {
        flush();
        closeStorages();
    }

    protected abstract void readBufferFromPage(Identifier pageIdentifier) throws IOException;

    protected abstract void writeBufferToPage(Identifier pageIdentifier) throws IOException;

    protected abstract void readMetaFromStorage() throws IOException;

    protected abstract void writeMetaToStorage() throws IOException;

    protected abstract void readIndicesFromStorage() throws IOException;

    protected abstract void writeIndicesToStorage() throws IOException;

    protected abstract void closeStorages() throws IOException;
}
